import math from "./decimal";

const r0 = cc.v2();
const r1 = cc.v2();
const r2 = cc.v2();
const r3 = cc.v2();

export default class intersection {
    /**
     * !#en Test line and line
     * !#zh 测试线段与线段是否相交
     * @method lineLine
     * @param {Vec2} a1 - The start point of the first line
     * @param {Vec2} a2 - The end point of the first line
     * @param {Vec2} b1 - The start point of the second line
     * @param {Vec2} b2 - The end point of the second line
     * @return {boolean}
     */
    static lineLine(a1: cc.Vec2, a2: cc.Vec2, b1: cc.Vec2, b2: cc.Vec2) {
        // jshint camelcase:false
        const ua_t = math.sub(
            math.mul(math.sub(b2.x, b1.x), math.sub(a1.y, b1.y)),
            math.mul(math.sub(b2.y, b1.y), math.sub(a1.x, b1.x))
        );

        const ub_t = math.sub(
            math.mul(math.sub(a2.x, a1.x), math.sub(a1.y, b1.y)),
            math.mul(math.sub(a2.y, a1.y), math.sub(a1.x, b1.x))
        );

        const u_b = math.sub(
            math.mul(math.sub(b2.y, b1.y), math.sub(a2.x, a1.x)),
            math.mul(math.sub(b2.x, b1.x), math.sub(a2.y, a1.y))
        );

        if (!u_b.isZero()) {
            const ua = math.div(ua_t, u_b);
            const ub = math.div(ub_t, u_b);

            if (math.gte(ua, 0) && math.lte(ua, 1) && math.gte(ub, 0) && math.lte(ub, 1)) {
                return true
            }
        }

        return false
    };

    /**
     * !#en Test line and rect
     * !#zh 测试线段与矩形是否相交
     * @method lineRect
     * @param {Vec2} a1 - The start point of the line
     * @param {Vec2} a2 - The end point of the line
     * @param {Rect} b - The rect
     * @return {boolean}
     */
    static lineRect(a1: cc.Vec2, a2: cc.Vec2, b: cc.Rect) {
        r0.x = b.x
        r0.y = b.y

        r1.x = b.x
        r1.y = b.yMax

        r2.x = b.xMax
        r2.y = b.yMax

        r3.x = b.xMax
        r3.y = b.y

        if (this.lineLine(a1, a2, r0, r1))
            return true;

        if (this.lineLine(a1, a2, r1, r2))
            return true;

        if (this.lineLine(a1, a2, r2, r3))
            return true

        if (this.lineLine(a1, a2, r3, r0))
            return true

        return false
    };

    /**
     * 测试一个点是否在一个矩形中
     */
    static pointInRect(point: cc.Vec2, rect: cc.Rect) {
        return math.gte(point.x, rect.xMin) &&
            math.lte(point.x, rect.xMax) &&
            math.gte(point.y, rect.yMin) &&
            math.lte(point.y, rect.yMax);
    }

    /**
     * !#en Test line and polygon
     * !#zh 测试线段与多边形是否相交
     * @method linePolygon
     * @param {Vec2} a1 - The start point of the line
     * @param {Vec2} a2 - The end point of the line
     * @param {Vec2[]} b - The polygon, a set of points
     * @return {boolean}
     */
    static linePolygon(a1: cc.Vec2, a2: cc.Vec2, b: cc.Vec2[]) {
        for (let i = 0; i < b.length; ++i) {
            const b1 = b[i]
            const b2 = b[(i + 1) % b.length]

            if (this.lineLine(a1, a2, b1, b2))
                return true
        }

        return false
    };

    /**
     * !#en Test rect and rect
     * !#zh 测试矩形与矩形是否相交
     * @method rectRect
     * @param {Rect} a - The first rect
     * @param {Rect} b - The second rect
     * @return {boolean}
     */
    static rectRect(a: cc.Rect, b: cc.Rect) {
        const a_min_x = a.x
        const a_min_y = a.y
        const a_max_x = math.add(a.x, a.width)
        const a_max_y = math.add(a.y, a.height)

        const b_min_x = b.x
        const b_min_y = b.y
        const b_max_x = math.add(b.x, b.width)
        const b_max_y = math.add(b.y, b.height)

        return math.lte(a_min_x, b_max_x) &&
            math.gte(a_max_x, b_min_x) &&
            math.lte(a_min_y, b_max_y) &&
            math.gte(a_max_y, b_min_y)
    };

    /**
     * !#en Test rect and polygon
     * !#zh 测试矩形与多边形是否相交
     * @method rectPolygon
     * @param {Rect} a - The rect
     * @param {Vec2[]} b - The polygon, a set of points
     * @return {boolean}
     */
    static rectPolygon(a: cc.Rect, b: cc.Vec2[]) {
        r0.x = a.x;
        r0.y = a.y;

        r1.x = a.x;
        r1.y = a.yMax;

        r2.x = a.xMax;
        r2.y = a.yMax;

        r3.x = a.xMax;
        r3.y = a.y;

        // intersection check
        if (this.linePolygon(r0, r1, b))
            return true;

        if (this.linePolygon(r1, r2, b))
            return true;

        if (this.linePolygon(r2, r3, b))
            return true;

        if (this.linePolygon(r3, r0, b))
            return true;

        // check if a contains b
        for (let i = 0, l = b.length; i < l; ++i) {
            if (this.pointInRect(b[i], a))
                return true;
        }

        // check if b contains a
        if (this.pointInPolygon(r0, b))
            return true;

        if (this.pointInPolygon(r1, b))
            return true;

        if (this.pointInPolygon(r2, b))
            return true;

        if (this.pointInPolygon(r3, b))
            return true;

        return false;
    };

    /**
     * !#en Test polygon and polygon
     * !#zh 测试多边形与多边形是否相交
     * @method polygonPolygon
     * @param {Vec2[]} a - The first polygon, a set of points
     * @param {Vec2[]} b - The second polygon, a set of points
     * @return {boolean}
     */
    static polygonPolygon(a: cc.Vec2[], b: cc.Vec2[]) {
        // check if a intersects b
        for (let i = 0, l = a.length; i < l; ++i) {
            const a1 = a[i];
            const a2 = a[(i + 1) % l];

            if (this.linePolygon(a1, a2, b))
                return true;
        }

        // check if a contains b
        for (let i = 0, l = b.length; i < l; ++i) {
            if (this.pointInPolygon(b[i], a))
                return true;
        }

        // check if b contains a
        for (let i = 0, l = a.length; i < l; ++i) {
            if (this.pointInPolygon(a[i], b))
                return true;
        }

        return false;
    };

    /**
     * !#en Test circle and circle
     * !#zh 测试圆形与圆形是否相交
     * @method circleCircle
     * @param {Object} a - Object contains position and radius
     * @param {Object} b - Object contains position and radius
     * @return {boolean}
     * @typescript circleCircle(a: {position: Vec2, radius: number}, b: {position: Vec2, radius: number}): boolean
     */
    static circleCircle(a: { position: cc.Vec2, radius: number }, b: { position: cc.Vec2, radius: number }) {
        const x = math.sub(a.position.x, b.position.x);
        const y = math.sub(a.position.y, b.position.y);

        // 这里直接用平方比较，理论上比开方效果更好
        const distance = math.add(math.pow(x, 2), math.pow(y, 2));
        return distance < math.pow(math.add(a.radius, b.radius), 2);

        // distance = math.sqrt(math.add(math.pow(x, 2), math.pow(y, 2)));
        // return distance < math.add(a.radius, b.radius);
    };

    /**
     * !#en Test polygon and circle
     * !#zh 测试矩形与圆形是否相交
     * @method polygonCircle
     * @param {Vec2[]} polygon - The Polygon, a set of points
     * @param {Object} circle - Object contains position and radius
     * @return {boolean}
     * @typescript polygonCircle(polygon: Vec2[], circle: {position: Vec2, radius: number}): boolean
     */
    static polygonCircle(polygon: cc.Vec2[], circle: { position: cc.Vec2, radius: number }) {
        const position = circle.position;
        if (this.pointInPolygon(position, polygon)) {
            return true;
        }

        for (let i = 0, l = polygon.length; i < l; i++) {
            const start = i === 0 ? polygon[polygon.length - 1] : polygon[i - 1];
            const end = polygon[i];

            if (math.lt(this.pointLineDistance(position, start, end, true), circle.radius)) {
                return true;
            }
        }

        return false;
    };

    /**
     * !#en Test whether the point is in the polygon
     * !#zh 测试一个点是否在一个多边形中
     * @method pointInPolygon
     * @param {Vec2} point - The point
     * @param {Vec2[]} polygon - The polygon, a set of points
     * @return {boolean}
     */
    static pointInPolygon(point: cc.Vec2, polygon: cc.Vec2[]) {
        let inside = false;
        const x = point.x;
        const y = point.y;

        // use some raycasting to test hits
        // https://github.com/substack/point-in-polygon/blob/master/index.js
        length = polygon.length;

        for (let i = 0, j = length - 1; i < length; j = i++) {
            const xi = polygon[i].x;
            const yi = polygon[i].y;
            const xj = polygon[j].x;
            const yj = polygon[j].y;

            const intersect = (math.gt(yi, y) !== math.gt(yj, y)) &&
                math.lt(x, math.add(math.div(math.mul(math.sub(xj, xi), math.sub(y, yi)), math.sub(yj, yi)), xi));

            if (intersect) inside = !inside;
        }

        return inside;
    };

    /**
     * !#en Calculate the distance of point to line.
     * !#zh 计算点到直线的距离。如果这是一条线段并且垂足不在线段内，则会计算点到线段端点的距离。
     * @method pointLineDistance
     * @param {Vec2} point - The point
     * @param {Vec2} start - The start point of line
     * @param {Vec2} end - The end point of line
     * @param {boolean} isSegment - whether this line is a segment
     * @return {boolean}
     */
    static pointLineDistance(point: cc.Vec2, start: cc.Vec2, end: cc.Vec2, isSegment: boolean) {
        return math.sqrt(this.pointLineDistance2(point, start, end, isSegment));
    };

    static pointLineDistance2(point: cc.Vec2, start: cc.Vec2, end: cc.Vec2, isSegment: boolean) {
        let px = null;
        let py = null;
        let dx = math.sub(end.x, start.x);
        let dy = math.sub(end.y, start.y);
        const d = math.add(math.pow(dx, 2), math.pow(dy, 2));
        const t = math.div(
            math.add(math.mul(math.sub(point.x, start.x), dx), math.mul(math.sub(point.y, start.y), dy)),
            d
        );

        if (!isSegment) {
            px = math.add(start.x, math.mul(t, dx));
            py = math.add(start.y, math.mul(t, dy));
        } else {
            if (d) {
                if (math.lt(t, 0)) {
                    px = start.x;
                    py = start.y;
                }
                else if (math.gt(t, 1)) {
                    px = end.x;
                    py = end.y;
                }
                else {
                    px = math.add(start.x, math.mul(t, dx));
                    py = math.add(start.y, math.mul(t, dy));
                }
            } else {
                px = start.x;
                py = start.y;
            }
        }

        dx = math.sub(point.x, px);
        dy = math.sub(point.y, py);
        return math.add(math.pow(dx, 2), math.pow(dy, 2));
    };
}